#ifndef PORTABLE_GLOBALS_H_
#define PORTABLE_GLOBALS_H_

#include <stdint.h>

#include <string>
#include <unordered_map>
#include <variant>

#include "src/pbc/portable-flags.h"
#include "src/pbc/portable-logging.h"
#include "src/pbc/portable-macros.h"
#include "src/pbc/portable-runtimes.h"
#include "src/objects/contexts.h"

namespace portable {

// Superclass for classes only using static method functions.
// The subclass of AllStatic cannot be instantiated at all.
class AllStatic {
#ifdef DEBUG
 public:
  AllStatic() = delete;
#endif
};

typedef uintptr_t Address;
static constexpr Address kNullAddress = 0;

// Returns true iff |value| is a power of 2.
template <typename T,
          typename = typename std::enable_if<std::is_integral<T>::value ||
                                             std::is_enum<T>::value>::type>
constexpr inline bool IsPowerOfTwo(T value) {
  return value > 0 && (value & (value - 1)) == 0;
}

// -----------------------------------------------------------------------------
// Constants

constexpr int kMaxInt = 0x7FFFFFFF;
constexpr int kMinInt = -kMaxInt - 1;
constexpr int kMaxInt8 = (1 << 7) - 1;
constexpr int kMinInt8 = -(1 << 7);
constexpr int kMaxUInt8 = (1 << 8) - 1;
constexpr int kMinUInt8 = 0;
constexpr int kMaxInt16 = (1 << 15) - 1;
constexpr int kMinInt16 = -(1 << 15);
constexpr int kMaxUInt16 = (1 << 16) - 1;
constexpr int kMinUInt16 = 0;
constexpr int kMaxInt31 = kMaxInt / 2;
constexpr int kMinInt31 = kMinInt / 2;
constexpr int kBitsPerByte = 8;

constexpr uint32_t kMaxUInt32 = 0xFFFFFFFFu;
constexpr int kMinUInt32 = 0;

constexpr int kInt8Size = sizeof(int8_t);
constexpr int kUInt8Size = sizeof(uint8_t);
constexpr int kByteSize = 1;
constexpr int kCharSize = sizeof(char);
constexpr int kShortSize = sizeof(short);  // NOLINT
constexpr int kInt16Size = sizeof(int16_t);
constexpr int kUInt16Size = sizeof(uint16_t);
constexpr int kIntSize = sizeof(int);
constexpr int kInt32Size = sizeof(int32_t);
constexpr int kInt64Size = sizeof(int64_t);
constexpr int kUInt32Size = sizeof(uint32_t);
constexpr int kSizetSize = sizeof(size_t);
constexpr int kFloat16Size = sizeof(uint16_t);
constexpr int kFloatSize = sizeof(float);
constexpr int kDoubleSize = sizeof(double);
constexpr int kIntptrSize = sizeof(intptr_t);
constexpr int kUIntptrSize = sizeof(uintptr_t);
constexpr int kSystemPointerSize = sizeof(void*);
constexpr int kSystemPointerHexDigits = kSystemPointerSize == 4 ? 8 : 12;

// Defines hints about receiver values based on structural knowledge.
enum class ConvertReceiverMode : unsigned {
  kNullOrUndefined,     // Guaranteed to be null or undefined.
  kNotNullOrUndefined,  // Guaranteed to never be null or undefined.
  kAny,                 // No specific knowledge about receiver.

  kLast = kAny
};

enum class Operation : uint8_t {
  kAdd,
  kSub,
  kMul,
  kDiv,
  kMod,
  kExp,
  kBitOr,
  kBitXor,
  kBitAnd,
  kShl,
  kSar,
  kShr,
  kInc,
  kDec,
  kBitNot,
  kEq,
  kEqStrict,
  kLessThan,
  kGreaterThan,
  kLessThanEq,
  kGreaterThanEq,
  kInstanceOf,
  kIn,
  kInvalid
};

enum class AbortReason : uint8_t { kNoReason, kLastErrorMessage };

// The Strict Mode (ECMA-262 5th edition, 4.2.2).
enum class LanguageMode : bool { kSloppy, kStrict };
static const size_t LanguageModeSize = 2;

// Indicates whether the lookup is related to sloppy-mode block-scoped
// function hoisting, and is a synthetic assignment for that.
enum class LookupHoistingMode { kNormal, kLegacySloppy };

enum class TypeofMode { kInside, kNotInside };

// Flags for the runtime function kDefineKeyedOwnPropertyInLiteral.
// - Whether the function name should be set or not.
enum class DefineKeyedOwnPropertyInLiteralFlag {
  kNoFlags = 0,
  kSetFunctionName = 1 << 0
};
using DefineKeyedOwnPropertyInLiteralFlags =
    Flags<DefineKeyedOwnPropertyInLiteralFlag>;
P_DEFINE_OPERATORS_FOR_FLAGS(DefineKeyedOwnPropertyInLiteralFlags)

enum class DefineKeyedOwnPropertyFlag {
  kNoFlags = 0,
  kSetFunctionName = 1 << 0
};
using DefineKeyedOwnPropertyFlags = Flags<DefineKeyedOwnPropertyFlag>;
P_DEFINE_OPERATORS_FOR_FLAGS(DefineKeyedOwnPropertyFlags)

// Flags for the yield operation.
// - Whether the yield expression should be iterable
enum class YieldIterationFlag { kNoIteration = 0, kIteration = 1 << 0 };
using YieldIterationFlags = Flags<YieldIterationFlag>;
P_DEFINE_OPERATORS_FOR_FLAGS(YieldIterationFlags)

enum class IteratorFinishFlag {
  kCanThrowException = 0,
  kIgnoreException = 1 << 0
};
using IteratorFinishFlags = Flags<IteratorFinishFlag>;

// Defines specifics about arguments object or rest parameter creation.
enum class CreateArgumentsType : uint8_t {
  kMappedArguments,
  kUnmappedArguments,
  kRestParameter
};

constexpr int kMin_Context_Slots = 2;
static_assert(v8::internal::Context::MIN_CONTEXT_SLOTS == 2);
constexpr int kExtension_Context_Slots = 3;
static_assert(v8::internal::Context::MIN_CONTEXT_EXTENDED_SLOTS == 3);

// This constant is used as an undefined value when passing source positions.
constexpr int kNoSourcePosition = -1;

template <typename V>
static inline void WriteUnalignedValue(Address p, V value) {
  memcpy(reinterpret_cast<void*>(p), &value, sizeof(V));
}

// ES6 6.1.7.1
enum PropertyAttributes {
  NONE = 0,
  READ_ONLY = 1 << 0,
  DONT_ENUM = 1 << 1,
  DONT_DELETE = 1 << 2,

  ALL_ATTRIBUTES_MASK = READ_ONLY | DONT_ENUM | DONT_DELETE,

  SEALED = DONT_DELETE,
  FROZEN = SEALED | READ_ONLY,

  ABSENT = 64,  // Used in runtime to indicate a property is absent.
  // ABSENT can never be stored in or returned from a descriptor's attributes
  // bitfield.  It is only used as a return value meaning the attributes of
  // a non-existent property.
};

class RegExp final {
 public:
  enum Flags {
    kNone = 0,
    kGlobal = 1 << 0,
    kIgnoreCase = 1 << 1,
    kMultiline = 1 << 2,
    kSticky = 1 << 3,
    kUnicode = 1 << 4,
    kDotAll = 1 << 5,
    kLinear = 1 << 6,
    kHasIndices = 1 << 7,
    kUnicodeSets = 1 << 8,
  };
};

// Important virtual machine structures !!!!
class Runtime final {
 public:
  enum FunctionId : int32_t {
#define FUNCTIONIDS(NAME) k##NAME,
    P_RUNTIME_FUNCTIONS(FUNCTIONIDS)
#undef FUNCTIONIDS
        kDeclareGlobals = 361,
    kNumOfFunctions,
    kInvalidFunctionId,
  };

  static std::string NameOfIndex(FunctionId id) {
    switch (id) {
#define FUNCTION_CASE(NAME) \
  case k##NAME:             \
    return #NAME;
      P_RUNTIME_FUNCTIONS(FUNCTION_CASE)
#undef FUNCTION_CASE
      case kDeclareGlobals:
        return "DeclareGlobals";
      default:
        P_UNREACHABLE();
    }
  }

  static FunctionId ToRuntimeFunctionId(int32_t id) {
    return static_cast<FunctionId>(id);
  }
};

// todo(hh): maybe we should use portable::context_index instead of
// i::Context::context_index in v8
class JSRuntime final {
 public:
  enum JSFunctionIndex : int {
    First_Index,
#define P_CONTEXT_INDEX(INDEX, JSFunc, NAME) INDEX,
    P_NATIVE_CONTEXT_FIELDS(P_CONTEXT_INDEX)
#undef P_CONTEXT_INDEX
        Last_Index,
  };

  static std::string NameOfIndex(int index) {
    P_DCHECK(index < static_cast<int>(Last_Index) &&
             index > static_cast<int>(First_Index));
    switch (index) {
#define FUNCTION_CASE(INDEX, JSFunc, NAME) \
  case JSFunctionIndex::INDEX:             \
    return #NAME;
      P_NATIVE_CONTEXT_FIELDS(FUNCTION_CASE)
#undef FUNCTION_CASE
      default:
        P_UNREACHABLE();
    }
  }

  static v8::internal::Context::Field ToContextField(JSFunctionIndex index) {
    switch (index) {
#define TRANSFORM_CASE(INDEX, JSFunc, NAME) \
  case (JSFunctionIndex::INDEX):            \
    return v8::internal::Context::INDEX;
      P_NATIVE_CONTEXT_FIELDS(TRANSFORM_CASE)
#undef TRANSFORM_CASE
      case (JSFunctionIndex::First_Index):
      case (JSFunctionIndex::Last_Index):
      default:
        P_UNREACHABLE();
    }
  }
  static v8::internal::Context::Field ToContextField(uint32_t index) {
    P_DCHECK(index < static_cast<int>(Last_Index) &&
             index > static_cast<int>(First_Index));
    return ToContextField(static_cast<JSFunctionIndex>(index));
  }
};

using String = std::variant<std::string, std::u16string>;
struct StringHasher {
  size_t operator()(const String& v) const {
    return std::visit(
        [](const auto& val) -> size_t {
          using T = std::decay_t<decltype(val)>;
          if constexpr (std::is_same_v<T, std::string>) {
            return std::hash<std::string>{}(val);
          } else if constexpr (std::is_same_v<T, std::u16string>) {
            return std::hash<std::u16string>{}(val);
          }
          return 0;
        },
        v);
  }
};

struct StringEqual {
  bool operator()(const String& lhs, const String& rhs) const {
    return std::visit(
        [](const auto& l, const auto& r) -> bool {
          using T1 = std::decay_t<decltype(l)>;
          using T2 = std::decay_t<decltype(r)>;
          if constexpr (std::is_same_v<T1, T2>) {
            return l == r;
          }
          return false;
        },
        lhs, rhs);
  }
};

}  // namespace portable

#endif  // PORTABLE_GLOBALS_H_